package it.antonio.ilpizze.db.core;

import it.antonio.ilpizze.db.DbException;
import it.antonio.ilpizze.db.core.fs.TransactionFile;

import java.util.LinkedList;
import java.util.List;

public class TransactionManager {

	private List<TransactionUnit> transactions = new LinkedList<TransactionUnit>();
	
	private DbFileSystem fs;
	
	private boolean autoCommit = false;
	
	private Transaction tx; 
	
	public TransactionManager(DbFileSystem fs) {
		this.fs = fs;
		this.tx = new SingleThreadTransaction();
	}

	public void openTransactionLog() {
		checkAutoCommitFalse();
		Integer txId = tx.allocateTx();
		TransactionFile logFile = fs.openTransactionLogFile(txId);
		tx.log(logFile);
	}

	public void add(TransactionUnit unit) {
		checkAutoCommitFalse();
		transactions.add(unit);
		unit.log(tx.log());	
	}

	public void commit() {
		checkAutoCommitFalse();
		while (!transactions.isEmpty()) {
			TransactionUnit unit = transactions.remove(0);
			unit.commit();
		}
		
	}

	public void rollback() {
		checkAutoCommitFalse();
		while (!transactions.isEmpty()) {
			TransactionUnit unit = transactions.remove(0);
			unit.rollback();
		}
	}

	public void disposeTransactionLog(){
		checkAutoCommitFalse();
		fs.disposeTransactionLog(tx.log());
		tx.dispose();
	}

	public void autoCommit(boolean ac) {
		this.autoCommit = ac;
	}
	
	public boolean autoCommit() {
		return autoCommit;
	}
	
	private void checkAutoCommitFalse(){
		if(autoCommit == true){
			throw new DbException("In autoCommit mode you cannot use transactions");
		}
	}
	public void multiThreading(boolean multiThreading) {
		if(multiThreading) {
			this.tx = new MultiThreadTransaction();	 
		} else {
			this.tx = new SingleThreadTransaction();	
		}
	}
		
	private interface Transaction{
		int allocateTx();
		void dispose();
		TransactionFile log();
		void log(TransactionFile log);
	}

	private class SingleThreadTransaction implements Transaction{

		private TransactionFile log;
		
		@Override
		public int allocateTx() {
			return 0;
		}
		@Override
		public TransactionFile log() {
			return log;
		}
		@Override
		public void log(TransactionFile log) {
			this.log = log;
		}
		@Override
		public void dispose() {
		}
	}
	
	private class MultiThreadTransaction implements Transaction{
		
		private int txCounter = -1; 
		private LinkedList<Integer> reusables = new LinkedList<Integer>(); 
		
		private ThreadLocal<TransactionFile> log = new ThreadLocal<TransactionFile>();
		
		@Override
		public int allocateTx() {
			if(reusables.isEmpty() == true){
				return txCounter++;
			} else {
				return reusables.get(0);	
			}
		}

		@Override
		public TransactionFile log() {
			return log.get();
		}

		@Override
		public void log(TransactionFile log) {
			this.log.set(log);
		}

		@Override
		public void dispose() {
			int id = this.log.get().id();
			if(id < txCounter){
				reusables.add(id);
			} else {
				txCounter--;
			}
		}
		
	}

	
}
